iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Build on Google AI

在贏家書寫歷史之前:我所看見的 AI,與一位工程師共舞著系列 第 3

Day 03 | 少年 Token(上):BPE 分詞演算法的奇幻起點

  • 分享至 

  • xImage
  •  

大家在剛開始綁定 OpenAI、Anthropic 或是 Google Cloud 信用卡時,多少都有過一個想法:明明我剛才只是在對話框裡輸入了幾百個中文字,為什麼看後台帳單時,上面寫的計價單位既不是「字數」、也不是「傳輸流量(MB)」,而是一個叫 Token 的東西?甚至不知不覺,這個月的額度就被吃掉了幾十萬個 Token,到底你的錢錢都去哪了?今天就盡可能用直白的語言,認識一下這個把你錢包吃乾抹淨的少年 Token 吧!

身為軟體工程師,難免習慣電腦世界用 Byte、Int、String 說話,所以我曾經也很可愛地以為 Token 是以 Unicode 為單位(還在我同事面前吹了一頓我對 Token 的理解,現在想想還真荒謬。上次這麼尷尬大概是大學教授問我電腦用的作業系統是什麼時,我回他「什麼是作業系統?」的時候)。當你踏進大語言模型(LLM)的宇宙,你必須先認識這個宇宙的基本單位:Token。它既不是純粹的一個「字」,也不是單純的一個「字母」,更像是一個字典裡的詞,它可以是一個字,例如:「我」;它也可以是一個詞,例如:「蘋果」、「apple」。模型只能理解這個字典裡的詞,只有在這個字典裡出現的詞,對於模型而言才是有意義的。就好像你在學英文時,在一句話中看到生字,去查了字典,發現它有收錄這個字,所以你能認得;但要是遇到字典裡沒有的字呢?你可能就會試圖再把字拆成幾個你看得懂的片段(字根、字首等等)拼在一起,然後來推測意思,但當然這時理解的意思就不一定是正確的了。透過這樣的方式,你才開始有能力理解一句話。而 Token 就是這些背過或是存在字典裡的詞,也是你用來理解一句話的基本單位。


1. 分詞演算法有哪些流派?

當我們談到大模型的分詞 (Tokenize) 技術時,你可能聽過這個名字:BPE(Byte-Pair Encoding)。但難道這世界上就只有一種建立分詞器 (Tokenizer) 的方法嗎?當然不是,主流分詞演算法有幾個流派:

  1. BPE
    最早其實是用在檔案壓縮,概念最單純:從最短的字母或 Byte 開始,誰最常相鄰出現,就把它跟它鄰居合併成一個新單字。這是當今主流 LLM 最常使用的分詞演算法。值得注意的是,這段完全是純演算法計算,並沒有 AI 模型的參與
  2. WordPiece
    Google 早期在經典模型 BERT 上採用的演算法。它跟 BPE 很像,但多了一道審查:它不是光看「誰出現的次數多」,而是會去評估「這兩個碎片到底是真的天生一對,還是只是碰巧常常排在一起?」
    例如:ofthe 在英文文章裡可能很常一起出現,但它們各自本來就是獨立常見字,硬把這兩個字黏成一個新詞 of the 毫無意義,只會浪費字典空間;相反地,NewYork 黏在一起變成 New York(紐約),合體後的意義就大於各自單獨存在。WordPiece 只有在確認這兩個碎片 「合體後比各自存在更有價值」 時,才批准合併。
  3. Unigram
    這是 Google 的另一個流派。它的邏輯跟前面兩者恰恰相反:如果前兩者是「從小雪球越滾越大」,Unigram 則是 「先窮舉,再修剪」。它會先從輸入的訓練語料庫裡,把所有可能的 1-gram、2-gram 到 N-gram 等子字串大量枚舉出來,建立一張龐大的候選詞池(N 是自己設定的 Token 最大長度);接著用機率去評估:「如果把這個詞砍掉,會對整份語料的理解造成多大損失?」 把那些可有可無、冗餘的碎片一輪一輪修剪掉,直到收斂成一本最精華的目標字典。

那為什麼到了現代主流 LLM 全都傾向選擇 BPE 流派 呢?原因也很現實:BPE 的工程實現最乾淨穩定,而且退到底層是 256 個 UTF-8 位元組(Bytes)! 意味著不管輸入的是各國語言、罕見 emoji、還是寫錯的二進位代碼,在最差的情況下都可以退化到底層 Byte 硬拆出來,解決了 遇到「詞表外(OOV)」單字就會崩潰的問題

2. 以 BPE 為例,這本字典怎麼被建立起來的?

難道是工程師手動幫模型 key-in 的嗎?當然不是!這本字典是模型在訓練之前,用演算法從大量文章裡「統計出來的」。非常粗淺的想像一下 BPE 演算法編字典的過程,一開始工程師會給這本字典設定一個容量上限(例如:10 萬個詞):

  1. 從最短長度的字開始
    一開始,字典裡沒有任何長單字,只有最基礎的 26 個英文字母、基礎標點符號,以及電腦底層的 Byte。這時候的字典非常薄,沒有任何詞是有意義的。
  2. 統計誰最常一起出現
    演算法會統計每個相鄰字元的出現頻率。它發現 th 最常黏在一起,那就乾脆把 th 合併成一個新單字存進字典;接著又發現後面最常跟著 e,那就再把 the 合併進去。每次都只把「最常」相鄰出現的碎片合併成新單字,刷爆 LeetCode、聰明的你一定馬上發現這其實就是 Greedy 演算法。你肯定也想到了一個問題:「這樣不是時間複雜度可能到 $O(N^2)$ 嗎?」確實,所以也有其優化的方法,例如:我們可以透過 Max-Heap 來記錄目前出現頻率最高的 token;用 Hash Map 記錄 token 的位置在哪等等。但這就是另一個故事了,我們只需要知道,時間複雜度是可以被優化的。
  3. 一直統計直到字典額滿
    這個合併過程不斷重複,把最常連續出現的碎片合併成新單字。當演算法合併出第 10 萬個相異的最熱門詞彙時,這本字典就完工了!

這就是為什麼模型的字典裡會有 "apple"、會有 "function"、會有 "import",因為在人類寫過的大量文章或程式碼中,這些詞出現的頻率實在太高了,早就收錄在字典裡了。

3. 為什麼不乾脆以「字」或「字母」為單位?

看到這裡可能會產生一個疑問,那為什麼不乾脆用更直覺的做法,例如:

  • 做法 A:乾脆全用「字母」當單位?(像 Unicode 那樣,a 就是 a,每個字都是 1 個 Token)
  • 做法 B:乾脆全用「單字」當單位?(字典收錄所有英語單字,不拆任何碎片)

你想得到的,歷史上的 AI 前輩們一定都想過,確實會遇到一些問題,例如:

  1. 如果純用「字母」
    字典確實會變得超小,幾百個字元就搞定。但代價是序列長度會暴增十倍!一句話本來只有 10 個單字,拆成字母可能變成 100 個 Token。別的先不考慮,你的 Token 帳單會先爆掉。而後面我們也會聊到,Transformer 計算注意力(Attention)的複雜度極高,輸入長度一暴增,算力消耗也會隨之暴增,模型會直接跑不動。
  2. 如果純用「單字」
    字典會直接炸掉!世界上的語言有無數種時態變形、複數、複合名詞,字典就算塞 100 萬個單字也塞不完。更慘的是,只要遇到一個稍微冷門的新詞(例如:iPhone Duo),模型就會直接傻眼當機(這就是所謂的 Out-of-Vocabulary, OOV)。

所以,BPE 這類演算法就在這中間取了一個平衡,用有限的字典大小,盡可能涵蓋世界上的所有文本,又不會讓整句話的 Token 量太多。


稍微了解一點 Token 從哪來以及 Tokenizer 是什麼後,明天來進行個小實驗吧!(我沒想好要怎麼做就是了 XD)


參考資料


上一篇
Day 02 | 我所看見的 AI:一個軟體工程師的告白(下)
下一篇
Day 04 | 少年 Token(中):SentencePiece 說文解字
系列文
在贏家書寫歷史之前:我所看見的 AI,與一位工程師共舞著10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言